package com.bestcem.xm.award.service.impl;

import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson.JSON;
import com.bestcem.xm.award.constant.Constants;
import com.bestcem.xm.award.constant.RedisKeyConstant;
import com.bestcem.xm.award.dao.AwardAwardDao;
import com.bestcem.xm.award.dao.AwardHistoryDao;
import com.bestcem.xm.award.dao.AwardWinDao;
import com.bestcem.xm.award.dao.DrawRecordDao;
import com.bestcem.xm.award.entity.pojo.AwardAwardDO;
import com.bestcem.xm.award.enums.AwardTypeEnum;
import com.bestcem.xm.award.enums.NotificationTypeEnum;
import com.bestcem.xm.award.enums.RecordStatusEnum;
import com.bestcem.xm.award.mq.dto.TicketNotificationTriggerDTO;
import com.bestcem.xm.award.service.DrawCommonService;
import com.bestcem.xm.award.service.dto.draw.AwardDetailDTO;
import com.bestcem.xm.award.service.mq.send.AwardMessageService;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author xa.zhang <xa.zhang@idiaoyan.com>
 * @version v1.0
 * @date 2021/7/16 14:58
 * @desc
 */
@Slf4j
@Service
public class DrawCommonServiceImpl implements DrawCommonService {

    @Resource
    private AwardAwardDao awardDao;
//    @Resource
//    private RabbitMqMessageSender mqMessageSender;
    @Resource
    private AwardHistoryDao awardHistoryDao;
    @Resource(name = "recordDaoMysql")
    private DrawRecordDao recordDao;
    @Resource(name = "winDaoMysql")
    private AwardWinDao awardWinDao;
//    @Autowired
//    private RabbitMqConstantConfig rabbitMqConstantConfig;
    @Resource
    private ThreadPoolTaskExecutor threadPoolDiscardPolicy;

    @Resource
    private AwardMessageService awardMessageService;

    @Resource
    private RedisService redisService;
    /**
     * 奖励通知
     *
     * @param awardId 奖励id
     */
    @Override
    public void updateAwardAndSendNotification(String awardId) {
        if (log.isInfoEnabled()) {
            log.info("updateAwardAndSendNotification awardId:{}", awardId);
        }
        int modifyCount = awardDao.updateCurCountAddOneById(awardId);
        if (modifyCount != 1) {
            log.error("更新失败");
        }
        AwardAwardDO award = awardDao.findById(awardId);
        int total = getAwardTotalNum(award);
        //上一步增加curCount，这一步肯定不为null
        if (log.isInfoEnabled()) {
            log.info("[award] updateAwardAndSendNotification 总数:{}, curCount:{}, 奖励信息:{}", total, award.getCurCount().intValue(), JSON.toJSONString(award));
        }
        if (total == award.getCurCount().intValue()) {
            int notifyTypeIndex = 0;
            if (award.getTType().intValue() == AwardTypeEnum.REDPACKET.getIndex()) {
                notifyTypeIndex = NotificationTypeEnum.RED_PACKET_AWARD.getIndex();
            } else if (award.getTType().intValue() == AwardTypeEnum.DRAW.getIndex()) {
                notifyTypeIndex = NotificationTypeEnum.DRAW_AWARD.getIndex();
            } else if (award.getTType().intValue() == AwardTypeEnum.NEW_DRAW.getIndex()) {
                notifyTypeIndex = NotificationTypeEnum.DRAW_AWARD.getIndex();
            } else {
                return;//未知类型
            }

            TicketNotificationTriggerDTO notificationTriggerDTO = new TicketNotificationTriggerDTO();
            notificationTriggerDTO.setProjectId(award.getProjectId());
            notificationTriggerDTO.setNTypeTag(awardId);
            notificationTriggerDTO.setNType(notifyTypeIndex);
            notificationTriggerDTO.setCount(total);

            try {
                if (log.isInfoEnabled()) {
                    log.info("send Award SendNotification data:{}", JSON.toJSONString(notificationTriggerDTO));
                }
//                mqMessageSender.sendMsg(rabbitMqConstantConfig.getAppBpDirectExchange(), rabbitMqConstantConfig.getAppBpRoutingKey(),
//                        AwardMqConstant.TICKET_NOTIFICATION_TRIGGER_TOPIC, notificationTriggerDTO);

                awardMessageService.notificationTriggerSend(notificationTriggerDTO);
            } catch (Exception e) {
                log.error("send Award SendNotification fail", e);
            }
        }
    }

    /**
     * 返回奖励中的奖励总数量
     *
     * @param award
     * @return
     */
    @Override
    public int getAwardTotalNum(AwardAwardDO award) {
        int total = 0;
        if (award == null || award.getTType() == null) {
            return total;
        }
        if (award.getTType().intValue() == AwardTypeEnum.DRAW.getIndex()) {
            // 各个奖项统计
            for (AwardAwardDO.Award tmp : award.getAwards()) {
                total += (tmp.getNum() == null ? 0 : tmp.getNum());
            }
        } else if (award.getTType().intValue() == AwardTypeEnum.REDPACKET.getIndex()
                && award.getBasic() != null
                && award.getBasic().getTotalCount() != null) {
            total += award.getBasic().getTotalCount();
        } else if (award.getTType().intValue() == AwardTypeEnum.NEW_DRAW.getIndex()) {
            // 各个奖项统计
            for (AwardAwardDO.Award tmp : award.getAwards()) {
                total += (tmp.getNum() == null ? 0 : tmp.getNum());
            }
        }


        return total;
    }

    @Override
    public void asyncRestoreAwardPool(String awardId) {
        if (StringUtils.isBlank(awardId)) {
            return;
        }
        String poolKey = String.format(RedisKeyConstant.AWARD_POOL, awardId);
        Boolean hasKey = redisService.hasKey(poolKey);
        //如果有奖池，那么不用恢复奖池
        if (hasKey) {
            return;
        }
        threadPoolDiscardPolicy.execute(() -> tryRestoreAwardPool(awardId));

    }

    /**
     * 注意手动提前结束时，pool还会存在。
     * commonValid 前面已经检查过award是否生效。
     *
     * @param awardId 奖励Id
     * @return void
     * @author xa.zhang
     * @date 2021/10/14 13:47
     */
    @Override
    public void tryRestoreAwardPool(String awardId) {
        String lockKey = String.format(RedisKeyConstant.AWARD_RESTORE_POOL_KEY, awardId);
        String poolKey = String.format(RedisKeyConstant.AWARD_POOL, awardId);
        Boolean hasKey = redisService.hasKey(poolKey);
        //如果有奖池，那么不用恢复奖池
        if (hasKey) {
            return;
        }
        AwardAwardDO awardAwardDO = awardDao.findById(awardId);
        if (awardAwardDO == null || awardAwardDO.getBasic() == null) {
            return;
        }
        Integer type = awardAwardDO.getTType();
        if (type == null) {
            return;
        }
        int total = getAwardTotalNum(awardAwardDO);
        //奖励已抽完
        if (awardAwardDO.getCurCount() != null && total == awardAwardDO.getCurCount().intValue()) {
            return;
        }

        //抽奖
        if (type.intValue() == AwardTypeEnum.DRAW.getIndex() || type.intValue() == AwardTypeEnum.NEW_DRAW.getIndex()) {
            if (Validator.isNotNull(awardAwardDO.getBasic().getTotalCount())) {
                hasKey = redisService.hasKey(poolKey);
                if (hasKey) {
                    return;
                }
                if (redisService.tryLock(lockKey, 1, TimeUnit.SECONDS)) {
                    try {
                        restoreDrawPool(awardAwardDO);
                    } finally {
                        redisService.unlock(lockKey);
                    }
                }
            }
        }
        //红包
        else if (type.intValue() == AwardTypeEnum.REDPACKET.getIndex()) {
            //预计参与人数
            Long forecastCount = awardAwardDO.getBasic().getProbability();
            if (forecastCount != null && forecastCount.longValue() > Constants.ZERO) {
                hasKey = redisService.hasKey(poolKey);
                if (hasKey) {
                    return;
                }
                if (redisService.tryLock(lockKey, 1, TimeUnit.SECONDS)) {
                    try {
                        restoreRedPacketPool(awardAwardDO);
                    } finally {
                        redisService.unlock(lockKey);
                    }
                }
            }
        } else {
            return;
        }
    }


    /**
     * 恢复抽奖奖池信息
     *
     * @param awardAwardDO 奖励信息
     * @return void
     * @author xa.zhang
     * @date 2021/10/13 15:30
     */
    private void restoreDrawPool(AwardAwardDO awardAwardDO) {
        log.info("restoreDrawPool start");
        String awardId = awardAwardDO.getId();


        //未中奖次数
        long notWinCount = recordDao.getDrawCountByIdAndStatus(awardId, null, RecordStatusEnum.NO.getIndex());
        //已中奖统计
        Map<Integer, Integer> winLevelNumMap = Maps.newHashMap();
        List<AwardDetailDTO> dtoList = recordDao.getAwardDetailByAgg(awardId, null, RecordStatusEnum.YES.getIndex());
        for (AwardDetailDTO awardDetailDTO : dtoList) {
            winLevelNumMap.put(awardDetailDTO.getLevel(), awardDetailDTO.getWinCount());
        }

        //总可中奖次数
        int totalWinCount = 0;
        // 抽奖奖池初始化
        List<String> pool = new ArrayList<>();
        for (int i = 0; i < awardAwardDO.getAwards().size(); i++) {
            AwardAwardDO.Award award = awardAwardDO.getAwards().get(i);
            if (award == null) {
                continue;
            }
            totalWinCount += award.getNum();
            Integer winNum = winLevelNumMap.get(award.getLevel());
            if (winNum == null) {
                winNum = 0;
            }
            //添加可中奖奖池
            for (int j = 0; j < award.getNum() - winNum; j++) {
                pool.add(String.valueOf(i));
            }
        }
        // 添加不中奖
        long len = awardAwardDO.getBasic().getTotalCount() - totalWinCount - notWinCount;
        for (int i = 0; i < len; i++) {
            pool.add(Constants.MINUS_ONE_STRING);
        }
        // 随机排序
        Collections.shuffle(pool);
        // 存入redis
        redisService.lLeftPushAll(String.format(RedisKeyConstant.AWARD_POOL, awardId), pool);
        // 兑奖码标志设定 False
        awardWinDao.updateUsedByAwardId(awardId, Boolean.FALSE);
        log.info("restoreDrawPool end");
    }


    /**
     * 恢复红包奖池
     * 红包日限额、账号限制、达到总条数 都不会记录到history表中
     *
     * @param awardAwardDO 奖励信息
     * @return void
     * @author xa.zhang
     * @date 2021/10/13 15:30
     */
    private void restoreRedPacketPool(AwardAwardDO awardAwardDO) {
        log.info("restoreRedPacketPool start");
        String awardId = awardAwardDO.getId();
        //红包中奖总数量
        Integer totalCount = awardAwardDO.getBasic().getTotalCount();
        //红包已发送总数量
        long winCount = awardHistoryDao.getWinCount(awardId);
        //预计参与人数
        Long forecastCount = awardAwardDO.getBasic().getProbability();
        //已尝试领取次数
        long tryDrawCount = awardHistoryDao.getTryDrawCount(awardId);

        //待领取次数
        long waitDrawCount = forecastCount - tryDrawCount;
        //待中奖次数
        long waitWinCount = totalCount - winCount;

        //创建奖池
        List pool = new ArrayList<String>();
        // 添加抽中红包 "1"
        for (int i = 0; i < waitWinCount; i++) {
            pool.add(Constants.ONE_STRING);
        }
        // 添加不抽中红包 "0"
        long len = waitDrawCount - waitWinCount;
        for (int i = 0; i < len; i++) {
            pool.add(Constants.ZERO_STRING);
        }
        // 随机排序
        Collections.shuffle(pool);
        // 存入redis
        redisService.lLeftPushAll(String.format(RedisKeyConstant.AWARD_POOL, awardId), pool);
        log.info("restoreRedPacketPool end");
    }


}
